<?php

/**
 * MarcConverter for convertation MARC record
 *
 * @author Sergey Dostovalov
 */

require_once 'MarcException.php';
require_once 'MarcRecord.php';
require_once 'MarcReader.php';
//require_once 'php-marc.php';
class MarcConverter {
     /**
     * rulse of convertation describes in JSON
     * @var const
     */
     const RULES_FORMAT_JSON  = 1;

     /**
     * $ruleArray contains rules after json decode
     * @var array
     */
    private $ruleArray;

    //true if array of rulse is correct
    private $rulesChecked = false;


    /**
     * @param string $jsonRules
     * for example $json = '{
     *      "245":{
     *              "a":[{"200":"a"}],
     *              "b":[{"200":"b"}],
     *              "i1":[{".":"1"}],
     *              "i2":[{"#":"1"}, {"1":"1"}]
     *             },
     *      "300":{
     *              "a":[{"400":"a"},{"400":"b"}]
     *             },
     *      "001":{
     *              "control":[{"010":"control"},{"999":"a"}]
     *            }
     *  }';
     *
     * @param const $format
     * format, which describes the rules:
     * MarcConverter::RULES_FORMAT_JSON
     *
     * @exception MarcConverterException
     */
    public function  __construct($stringRules,
                                $format = self::RULES_FORMAT_JSON)
    {
        if ($format === self::RULES_FORMAT_JSON){
            $this->ruleArray = json_decode($stringRules, true);
            if (!$this->ruleArray){
                $message = 'JSON rules can\'t decode, incorrect JSON syntax';
                throw new MarcConverterException($message);
            }
            try{
                $this->checkRule($this->ruleArray);
            }
            catch (MarcConverterException $e){
                throw $e;
            }
            $this->rulesChecked = true;
        }
    }
    /**
     * @param string $marc raw MARC record(s)
     * @param array MarcRecord $marc instance of MarcRecord record
     * @return MarcRecord object
     */

    public function convertRecords(&$marc,  $extract = true , $encoding = 'UTF-8')
    {
        $marcReader = new MarcReader($marc, MarcReader::SOURSE_RAW, $encoding);
        $recordsArray = array();
        while($record = $marcReader->next()){
              $convertedRecord = $this->convertMarcRecord($record, $extract);
              unset($record);
              if (!count($convertedRecord->getFields())) continue;
              $recordsArray[] = $convertedRecord;
              unset($convertedRecord);
        }
        return $recordsArray;
    }

     /**
     * @param string $record MarcRecord record
     * @param bool $extract if true - extract fields from old record
     * @return MarcRecord $record converted record
     * @return null
     */
    public function convertMarcRecord(MarcRecord $record, $extract = false)
    {
        if (!$this->rulesChecked) return null;
        $newRecord = new MarcRecord();
        $fields = $record->getFields();
        unset($record);
        if ($fields === null) return null;
        foreach ($fields as $tag=>$fields_group) {
            $fieldRules = $this->getRuleForField($tag);
            foreach($fields_group as $field){
                if (!$fieldRules){
                    if (!$extract) $newRecord->addField($field);
                    unset($fields_group);continue;
                }
                if ($field->isControlField()) {
                    foreach ($fieldRules['control'] as $fieldRule){
                        foreach ($fieldRule as $toFieldTag=>$toSubfieldCode){
                            if ((int)$toFieldTag > 0 and (int)$toFieldTag < 10){
                                $newControlField = new MarcControlField($toFieldTag, $field->getData());
                                $newRecord->addField($newControlField);
                            }
                            else{
                                $newDataField = new MarcDataField($toFieldTag);
                                $newDataSubfield = new MarcSubfield($toSubfieldCode, $field->getData());
                                $newDataField->addSubfield($newDataSubfield);
                                $newRecord->addField($newDataField);
                                unset($newDataSubfield);
                                unset($newDataField);
                            }
                        }
                        unset($toSubfieldCode);
                    }
                    unset($fieldRule);
                }
                //if field is data field
                else {
                    $newDataFields = array();
                    foreach ($field->getSubfields() as $code=>$subdataArray) {
                        $subfieldRules = &$this->getRuleForSubfield($tag, $code);
                        foreach ($subdataArray as $subdata){                        
                            if ($subfieldRules){
                                $newSubfields  = array();
                                foreach ($subfieldRules as $subfieldRule){
                                    foreach ($subfieldRule as $toFieldTag=>$toSubfieldCode){
                                        $newSubfields[$toFieldTag][] = new MarcSubfield($toSubfieldCode, $subdata->getData());
                                    }unset($toSubfieldCode);
                                }       
                                foreach ($newSubfields as $toFieldTag=>$subfields){
                                    foreach ($subfields as $subfield){
                                        if(!isset ($newDataFields[$toFieldTag])){
                                            $newDataFields[$toFieldTag] = new MarcDataField($toFieldTag);
                                        }
                                        $newDataFields[$toFieldTag]->addSubfield($subfield);
                                    }unset($subfield);
                                    
                                }unset($subfields);
                            }
                        }unset($subdata);
                    }unset($subdataArray);
                    foreach($newDataFields as $ntag=>$nfield){
                        foreach(array(1,2) as $indicator){
                            $indicatorRules = &$this->getRuleForIndicator($tag,$ntag ,$indicator);
                            if (!$indicatorRules){
                                $nfield->setIndicator($indicator, $field->getIndicator($indicator));
                                unset($indicator);continue;
                            }
                            $newIndValue = $this->getRuleForIndicator($tag, $ntag, $indicator, $field->getIndicator($indicator));
                            if ($newIndValue !== null){
                                if ($newIndValue === '#' )
                                    $nfield->setIndicator($indicator, ' ');
                                else 
                                    $nfield->setIndicator($indicator, $newIndValue);
                            }
                        }unset($indicator);
                        $newRecord->addField($nfield);
                    }unset($nfield);
                }
            }unset($field); 
        } unset($fields_group);
        return $newRecord;
    }

    /**
     * Return rules for marc data field in assoc array
     * @return array
     * @return null
     */
    public function getRulesArray()
    {
        if ($this->rulesChecked) return $this->ruleArray;
        else return null;
    }

     /**
     * Return rules for marc data field in assoc array
     * @param string $field
     * @return array
     * @return null
     */
    private function getRuleForField($field)
    {
        if (isset($this->ruleArray[$field])) return $this->ruleArray[$field];
        else return null;
    }

     /**
     * Return rules for subfield in marc data field in assoc array
     * @param string $field tag of data field
      *@param string $subfield tag of subfield in data field
     * @return array
     * @return null
     */
    private function getRuleForSubfield($field, $subfield)
    {
        if (isset($this->ruleArray[$field]) and
            isset($this->ruleArray[$field][$subfield])){
                return $this->ruleArray[$field][$subfield];
            }
        else return null;
    }

     /**
     * Return rules for indicators in marc data field
     * @param string $fromField tag of data field from
     *@param string $toField tag of data field to
     *@param string $indicator indicator tag 'i1' or 'i2'
      * @param string $value find value in rules
      *
     * @return array assoc array of indicator rules
     * @return string if value != null return new value for indicator
     * @return null if no rules for indicator
     */
    private function getRuleForIndicator($fromField, $toField, $indicator, $value = null)
    {
        if (!$this->rulesChecked) return null;
        //O_o
        if (isset($this->ruleArray['indicators']) and
            isset($this->ruleArray['indicators'][$fromField]) and
            isset($this->ruleArray['indicators'][$fromField][$toField]) and
            isset($this->ruleArray['indicators'][$fromField][$toField][$indicator])){
                if ($value !== null){
                    $indicatorRules =  &$this->ruleArray['indicators'][$fromField][$toField][$indicator];
                    foreach($indicatorRules as $indicatorRule){
                        //in first Pass find '.'
                        foreach ($indicatorRule as $oldValue=>$newValue){
                            if ($oldValue === '.') return $newValue;
                        }
                        foreach ($indicatorRule as $oldValue=>$newValue){
                            if ($value === ' ' and $oldValue === '#') return $newValue;
                            if ($oldValue == $value) return $newValue;
                        }

                    }
                    return null;
                }
                return $this->ruleArray['indicators'][$fromField][$toField][$indicator];
            }
        else return null;
    }

    /**
     * @param array $ruleArray
     * fromFieldsArray(
     *      "fromField1"=>fromSubfieldsArray(
     *                  "fromSubfield1"=>subfiledRulesArray(
     *                                  subfieldRule1Array(toField, toSubfield),
     *                                  subfieldRuleNArray(toField, toSubfield)
     *                                 ),
     *                  "fromSubfieldN"=>subfiledRulesArray(
     *                                  subfieldRule1Array(toField, toSubfield),
     *                                  subfieldRuleNArray(toField, toSubfield)
     *                                 )
     *
     *          )
     *      "fromFieldN"=>......
     * )
     * @exception MarcConverterException
     *
     */
    public function checkRule(array &$ruleArray)
    {
        function checkField($field){
            if (!is_numeric($field) or !is_int((int)$field)){
                $message = 'field number must be in range 1 to 999. Now - '.$field;
                throw new MarcConverterException($message);
            }
            if ($field < 1 or $field > 999){
                $message = 'field number must be in range 1 to 999. Now - '.$field;
                throw new MarcConverterException($message);
            }
        }

        if (!count($ruleArray)){
            $message = 'no input rules or format wrong';
            throw new MarcConverterException($message);
        }
        if (isset($ruleArray['indicators']) and count($ruleArray['indicators']) > 0){
            foreach($ruleArray['indicators'] as $fromField=>$fromFieldRules){
                $fromField = trim($fromField);
                checkField($fromField);
                if (!is_array($fromFieldRules) or !count($fromFieldRules)){
                    $message = 'no rules or format wrong in indicators on field '.$fromField;
                    throw new MarcConverterException($message);
                }
                foreach ($fromFieldRules as $toField=>$toFieldRules){
                    $toField = trim($toField);
                    checkField($toField);
                    if (!is_array($toFieldRules) or !count($toFieldRules)){
                        $message = 'no rules for indicator on "from field" '.$fromField.
                        ' and "to field" '.$toField;
                        throw new MarcConverterException($message);
                    }
                    foreach($toFieldRules as $indicator=>$indicatorRules){
                        $indicator = trim($indicator);
                        if ($indicator !== '1' and $indicator !== '2'){
                            $message = 'indicator on "from field" '.$fromField.
                            ' and "to field" '.$toField.' not correct. Now - '.$indicator;
                            throw new MarcConverterException($message);
                        }
                        if (!is_array($indicatorRules) or !count($indicatorRules)){
                            $message = 'no rules for indicator '.$indicator.
                            ' on "from field" '.$fromField.' and "to field" '.$toField;
                            throw new MarcConverterException($message);
                        }
                        foreach($indicatorRules as $indicatorRule){
                            if (!is_array($indicatorRule) or !count($indicatorRule)){
                                $message = 'no rules for indicator '.$indicator.
                                ' on "from field" '.$fromField.' and "to field" '.$toField;
                                throw new MarcConverterException($message);
                            }
                            foreach ($indicatorRule as $oldValue=>$newValue){
                                $oldValue = trim($oldValue);
                                $newValue = trim($newValue);
                                if (preg_match('/^[0-9]{1}$|#|\.$/', $oldValue) === 0 or
                                    preg_match('/^[0-9]{1}$|#$/', $newValue) === 0    ){
                                    $message = 'not correct value of indicators '.$indicator.
                                    ' on "from field" '.$fromField.' and "to field" '.$toField;
                                    throw new MarcConverterException($message);
                                }
                            } unset($oldValue);unset($newValue);
                        } unset($indicatorRule);
                    };unset($indicatorRules);
                }unset($toFieldRules);
            } unset($fromFieldRules);
        }
        else{
            if (isset($ruleArray['indicators'])){
                $message = 'indicators section defined but not have rules';
                                    throw new MarcConverterException($message);
            }
        }

       //checking of indicators block is finished
       $tempRulesArray = $ruleArray;
       //delete indicators path of array to exclude from checking
       unset($tempRulesArray['indicators']);

        foreach($tempRulesArray as $fromField=>$fromSubfields){
            checkField($fromField);
            if (!is_array($fromSubfields) or !count($fromSubfields)){
                $message = 'no rules or format wrong on from field '.$fromField;
                throw new MarcConverterException($message);
            }
            foreach($fromSubfields as $fromSubfield=>$fromSubfieldRules){
                //remove space simbols from begin and end of string
                $fromSubfield = trim($fromSubfield);
                //$fromSubfield be equal to a-z 0-9 (1 simbol) or  "i1" or "i2" or "control"
                if (preg_match('/^[a-z0-9]{1}$|control$/', $fromSubfield) === 0){
                    $message = 'format wrong on "from subfield" in '.$fromField.
                        ' "from field". Now - '.$fromSubfield;
                    throw new MarcConverterException($message);
                }
                if (!is_array($fromSubfieldRules) or !count($fromSubfieldRules)){
                    $message = 'no rules or format wrong on "from subfield" '.$fromSubfield.
                        ' in "from field '. $fromField;
                    throw new MarcConverterException($message);
                }
                foreach($fromSubfieldRules as $fromSubfieldRule){

                    if (!is_array($fromSubfieldRule) or !count($fromSubfieldRule)){
                        $message = 'no rules or format wrong on "from subfield" '.$fromSubfield.
                            ' in "from field '. $fromField;
                        throw new MarcConverterException($message);
                    }
                    foreach($fromSubfieldRule as $toField=>$toSubfield){
                        $toField = trim($toField);
                        $toSubfield = trim($toSubfield);
                        //Now $toField must be integer from 1 to 999,
                        //if not - return false
                        if (!is_numeric($toField) or !is_int((int)$toField)){
                            $message = '"to field" in rule "from subfield" <'.$fromSubfield.
                                '> in "from field" '. $fromField.' have wrong value - '. $toField;
                            throw new MarcConverterException($message);
                        }
                        if ($toField > 0 and  $toField <= 999){
                            //$toField consist number of field then
                            //$toSubfield must equal to a-z or 0-9 or "control"
                            if (preg_match('/^[0-9a-z]{1}$|control$/', $toSubfield) === 0){
                                $message = '"to subfield" in rule "from subfield" '.$fromSubfield.
                                    ' in "from field '. $fromField.' have wrong value - '.$toSubfield;
                                throw new MarcConverterException($message);
                            }
                            if ($toField > 9 and $toSubfield === 'control'){
                                $message = 'Subfield in data field with tag > 9 can\'t be "control"';
                                throw new MarcConverterException($message);
                            }
                        }
                        else {
                            $message = '"to field" in rule "from subfield" <'.$fromSubfield.
                                '> in "from field" '. $fromField.' have wrong value - '. $toField;
                            throw new MarcConverterException($message);
                        }
                        
                    }
                }unset($fromSubfieldRule);
            }unset($fromSubfieldRules);
        }unset($fromSubfields);
        $this->rulesChecked = true;
        $this->ruleArray = $ruleArray;
    }// end checkRule function
}
?>
